﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Media;
using System.Diagnostics;
using System.Collections;

namespace FireFly
{
    /// <summary>
    /// Collection of view models. Can be shown as separate group
    /// </summary>
    /// <typeparam name="TModel">type of source collection's elements</typeparam>
    /// <typeparam name="TViewModel">type of view model elements of this collection. MUST be ViewModel{T} or CollectionViewModel{T, VMT} or inherits one of them</typeparam>
    public class CollectionViewModel<TModel, TViewModel> : ObservableCollection<TViewModel>, ICollectionViewModel, IModelContainer
        where TViewModel : IViewModel
    {
        #region .ctor and fields
        IList<TModel> _list;
        bool _isModifying;
        Func<TModel, TViewModel> _constructor = null;
        string _DisplayName;
        IHierarchicalViewModel _parent;
        CollectionShowMode _showMode = CollectionShowMode.UserFriendly;

        /// <summary>
        /// Contructor
        /// </summary>
        /// <param name="list">source collection</param>
        /// <param name="constructor">factory method which will be used for producing new view model elements</param>
        public CollectionViewModel(IList<TModel> list, Func<TModel, TViewModel> constructor)
        {
            _isModifying = true;
            _list = list;
            _constructor = constructor;

            if(_list is INotifyCollectionChanged)
                ((INotifyCollectionChanged)_list).CollectionChanged +=
                new NotifyCollectionChangedEventHandler(IntrernalCollectionChanged);
            
            ShowMode = CollectionShowMode.UserFriendly;
            foreach(var model in list)
            {
                Add(ViewModelService.Instance.CreateViewModel<TModel, TViewModel>(model, this, _constructor));
            }
            _isModifying = false;
        }

        public CollectionViewModel(IList<TModel> list, CollectionShowMode mode, Func<TModel, TViewModel> constructor) 
            : this(list, constructor)
        {
            ShowMode = mode;
        }

        public CollectionViewModel(IList<TModel> list, string displayName, CollectionShowMode mode, Func<TModel, TViewModel> constructor)
            : this(list, mode, constructor)
        {
            _DisplayName = displayName;
        }
        #endregion
      
        #region internal collection synchronization
        /// <summary>
        /// True, if source collection is INotifyCollectionChanged
        /// </summary>
        public bool IsSourceCollectionObservable
        {
            get
            {
                return _list is INotifyCollectionChanged;
            }
        }

        /// <summary>
        /// Syncronizes with the source collection when it changed
        /// </summary>
        protected void IntrernalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if(_isModifying)
                return;

            _isModifying = true;

            try
            {
                if(e.OldStartingIndex > -1)
                {
                    this.RemoveAt(e.OldStartingIndex);
                }
                else if(e.OldItems != null)
                {
                    HashSet<TModel> set = new HashSet<TModel>();
                    foreach(var item in e.OldItems)
                    {
                        set.Add((TModel)item);
                    }
                    for(int i = this.Count - 1; i >= 0; i--)
                    {
                        if(set.Contains((TModel)((IModelContainer)this[i]).GetModel()))
                        {
                            this.RemoveAt(i);
                        }
                    }
                }

                if(e.NewStartingIndex > -1)
                {
                    this.Insert(e.NewStartingIndex, 
                        ViewModelService.Instance.CreateViewModel<TModel, TViewModel>(_list[e.NewStartingIndex], this, _constructor));
                }
                else if(e.NewItems != null)
                {
                    foreach(TModel item in e.NewItems)
                    {
                        this.Add(ViewModelService.Instance.CreateViewModel<TModel, TViewModel>(item, this, _constructor));
                    }
                }

                if(Parent!=null)
                    Parent.RaiseVisualChildrenUpdated();
            }
            finally
            {
                _isModifying = false;
            }

        }

        /// <summary>
        /// Syncronizes with the source collection, when current collection changed
        /// </summary>
        protected override sealed void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if(_isModifying)
            {
                base.OnCollectionChanged(e);
            }
            else
            {
                _isModifying = true;

                try
                {
                    if(e.OldStartingIndex > -1)
                    {
                        _list.RemoveAt(e.OldStartingIndex);
                    }
                    else if(e.OldItems != null)
                    {
                        for(int i = 0; i < e.OldItems.Count; i++)
                        {
                            _list.Remove((TModel)((IModelContainer)e.OldItems[i]).GetModel());
                        }
                    }

                    if(e.NewItems != null)
                    {
                        int index = e.NewStartingIndex > -1 ? e.NewStartingIndex : _list.Count;
                        for(int i = 0; i < e.NewItems.Count; i++)
                        {
                            _list.Insert(index + i, (TModel)((IModelContainer)e.NewItems[i]).GetModel());
                        }
                    }

                    base.OnCollectionChanged(e);
                    if(Parent!=null)
                        Parent.RaiseVisualChildrenUpdated();
                }
                finally
                {
                    _isModifying = false;
                }
            }
        }
        #endregion

        #region Properties

        /// <summary>
        /// Icon of collection. Defaults 
        /// </summary>
        public virtual ImageSource Icon
        {
            get
            {
                return ViewModelService.Instance.GetIcon("CollectionViewModel."+typeof(TModel).Name) ??
                    ViewModelService.Instance.GetIcon("CollectionViewModel");
            }
        }

        public virtual string DisplayName
        {
            get
            {
                return _DisplayName;
            }
            set
            {
                _DisplayName = value;
                OnPropertyChanged(new PropertyChangedEventArgs("DisplayName"));
            }
        }
        
        public virtual bool IsReadOnly
        {
            get
            {
                return Parent != null ? Parent.IsReadOnly : false;
            }
        }

        public IHierarchicalViewModel Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                _parent = value;
                OnPropertyChanged("Parent");
            }
        }

        public virtual ICollection VisualChildren
        {
            get
            {
                return this;
            }
        }

        public CollectionShowMode ShowMode
        {
            get
            {
                return _showMode;
            }
            set
            {
                _showMode = value;
                OnPropertyChanged("ShowMode");
            }
        }

        protected void OnPropertyChanged(string propName)
        {
            CheckPropertyName(propName);
            OnPropertyChanged(new PropertyChangedEventArgs(propName));
        }

        protected override sealed void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
        }

        public virtual IEnumerable<IRelayCommand> Commands
        {
            get
            {
                return new IRelayCommand[] { };
            }
        }
        #endregion

        #region SelectCommand
        public virtual IRelayCommand SelectCommand
        {
            get
            {
                return null;
            }
        }
        #endregion

        #region RemoveCommand
        public IRelayCommand RemoveCommand
        {
            get
            {
                if(_RemoveCommand == null)
                {
                    _RemoveCommand = new CommonRemoveItemCommand<CollectionViewModel<TModel, TViewModel>>("CollectionViewModel.RemoveCommand");
                }
                return _RemoveCommand;
            }
        }
        static IRelayCommand _RemoveCommand;
        #endregion

        #region IModelContainer Members

        object IModelContainer.GetModel()
        {
            return _list;
        }

        #endregion

        #region debug method: CheckPropertyName()
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        void CheckPropertyName(string propName)
        {
            if(TypeDescriptor.GetProperties(this)[propName] == null)
                throw new ArgumentOutOfRangeException(propName, string.Format("Property '{0}' not found", propName));
        }
        #endregion

        #region IHierarchicalViewModel Members


        public void RaiseVisualChildrenUpdated()
        {
            OnPropertyChanged("Children");
        }

        #endregion
    }
}